[Jest] clearAllMocks()とresetAllMocks()の違いについて確認してみた
こんにちは、CX事業本部 IoT事業部の若槻です。
JavaScriptのテスティングフレームワークJestでテストをする際に、テスト間のモックをクリアするためにclearAllMocks()
をよく使用しています。しかし最近似たようなオブジェクトとしてresetAllMocks()
なるものがあることを知りました。
今回は、clearAllMocks()
とresetAllMocks()
の違いを確認してみました。
clearAllMocks()の動作
まずclearAllMocks()
についてです。
Clears the
mock.calls
,mock.instances
andmock.results
properties of all mocks. Equivalent to calling.mockClear()
on every mocked function.
実行すると、すべてのモックの mock.calls
、mock.instances
およびmock.results
をクリアしてくれるとのこと。
動作確認として、次のようなmodule_b
を呼び出すmodule_a
のテストを考えてみます。
import * as ModuleB from "./module_b"; export type UserType = "manager" | "general"; /** * ユーザーがデータを取得する * @param userType ユーザータイプ */ export const getDataByUser = async (userType: UserType): Promise<string> => { if (userType === "manager") { return await ModuleB.getDataByManager(); } userType === "general"; { return await ModuleB.getDataByGeneralUser(); } };
/** * 管理者ユーザーがデータを取得する */ export const getDataByManager = async (): Promise<string> => { return "data for manager"; }; /** * 一般ユーザーがデータを取得する */ export const getDataByGeneralUser = async (): Promise<string> => { return "data for general"; };
module_a
のテストです。afterEach()
により各テスト(it()
)終了後にclearAllMocks()
を実行するようにしています。
import * as ModuleA from '../src/module_a'; import * as ModuleB from '../src/module_b'; describe('module_a', () => { afterEach(() => { jest.clearAllMocks(); }); (ModuleB.getDataByManager as jest.Mock) = jest.fn().mockReturnValue('dummy'); (ModuleB.getDataByGeneralUser as jest.Mock) = jest .fn() .mockReturnValue('dummy'); it('By Manager', async (): Promise<void> => { const res = await ModuleA.getDataByUser('manager'); expect(res).toBe('dummy'); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); it('By Manager', async (): Promise<void> => { const res = await ModuleA.getDataByUser('manager'); expect(res).toBe('dummy'); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); });
jestを実行するとテストがPASSしました。
$ npx jest PASS test/module_a.test.ts (8.73 s) module_a ✓ By Manager (2 ms) ✓ By GeneralUser (1 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 9.009 s Ran all test suites.
テスト間でmock.calls
がクリアされてtoHaveBeenCalledTimes()
やtoHaveBeenCalledTimes()
によるassertionが適切に行えています。これがclearAllMocks()
を使うことによる嬉しみです。
resetAllMocks()
次にresetAllMocks()
についてです。
Resets the state of all mocks. Equivalent to calling .mockReset() on every mocked function.
実行すると、すべてのモックのstateをクリア(初期化)してくれるとのこと。stateってなんなんでしょうね。
先程のテストのclearAllMocks()
をresetAllMocks()
に置き換えて動作を見てみます。
import * as ModuleA from '../src/module_a'; import * as ModuleB from '../src/module_b'; describe('module_a', () => { afterEach(() => { jest.resetAllMocks(); }); (ModuleB.getDataByManager as jest.Mock) = jest.fn().mockReturnValue('dummy'); (ModuleB.getDataByGeneralUser as jest.Mock) = jest .fn() .mockReturnValue('dummy'); it('By Manager', async (): Promise<void> => { const res = await ModuleA.getDataByUser('manager'); expect(res).toBe('dummy'); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); it('By Manager', async (): Promise<void> => { const res = await ModuleA.getDataByUser('manager'); expect(res).toBe('dummy'); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); });
するとテストがFAILしました。it()
の外で記述しているmockReturnValue()
によるモックが、1つ目のテストの後にリセットされてしまっているような動作ですね。
$ npx jest FAIL test/module_a.test.ts (5.097 s) module_a ✓ By Manager (2 ms) ✕ By Manager (2 ms) ● module_a › By Manager expect(received).toBe(expected) // Object.is equality Expected: "dummy" Received: undefined 22 | it('By Manager', async (): Promise<void> => { 23 | const res = await ModuleA.getDataByUser('manager'); > 24 | expect(res).toBe('dummy'); | ^ 25 | //getDataByManagerが1回実行されることを期待 26 | expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); 27 | expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); at Object.<anonymous> (test/module_a.test.ts:24:17) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 passed, 2 total Snapshots: 0 total Time: 5.139 s, estimated 6 s Ran all test suites.
そこで、それぞれのit()
内でmockReturnValue()
によるモックをするようにすればテストはPASSしました。
import * as ModuleA from '../src/module_a'; import * as ModuleB from '../src/module_b'; describe('module_a', () => { afterEach(() => { jest.resetAllMocks(); }); it('By Manager', async (): Promise<void> => { (ModuleB.getDataByManager as jest.Mock) = jest .fn() .mockReturnValue('dummy'); (ModuleB.getDataByGeneralUser as jest.Mock) = jest .fn() .mockReturnValue('dummy'); const res = await ModuleA.getDataByUser('manager'); expect(res).toBe('dummy'); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); it('By Manager', async (): Promise<void> => { (ModuleB.getDataByManager as jest.Mock) = jest .fn() .mockReturnValue('dummy'); (ModuleB.getDataByGeneralUser as jest.Mock) = jest .fn() .mockReturnValue('dummy'); const res = await ModuleA.getDataByUser('manager'); expect(res).toBe('dummy'); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); });
$ npx jest PASS test/module_a.test.ts module_a ✓ By Manager (2 ms) ✓ By Manager Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 0.73 s, estimated 6 s Ran all test suites.
結局両者の違いは何なのか
結局両者の違いは何なのか、clearAllMocks()
とresetAllMocks()
を個別のモックに対して実施するmockClear()
とmockReset()
のドキュメントの記述を見てみると、ちゃんと具体的に書いてありました。
Clears all information stored in the mockFn.mock.calls, mockFn.mock.instances and mockFn.mock.results arrays. Often this is useful when you want to clean up a mocks usage data between two assertions.
Does everything that mockFn.mockClear() does, and also removes any mocked return values or implementations.
This is useful when you want to completely reset a mock back to its initial state. (Note that resetting a spy will result in a function with no return value).
記述の要点をまとめてみると、次のようになります。
mockClear()
は、2つのassertion間でモックをクリーンアップしたい際に便利。mockReset()
は、mockClear()
の実行内容に加えて、return values or implementations
を削除可能。mockReset()
は、モックのstateを完全にリセットしたい際に便利。
なるほど。大きな違いとしてはreturn values or implementations
が削除されるかどうか、のようですね。前節までの検証での動作にも合致します。
参考
- [Jest] 呼び出す関数をconditionalに変えるコードのテストではclearAllMocks()を使おう | DevelopersIO
- Jest mockClear(), mockReset(), mockRestore() の違い - Qiita
以上